/*  Starshatter OpenSource Distribution
    Copyright (c) 1997-2004, Destroyer Studios LLC.
    All Rights Reserved.

    Redistribution and use in source and binary forms, with or without
    modification, are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright notice,
      this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright notice,
      this list of conditions and the following disclaimer in the documentation
      and/or other materials provided with the distribution.
    * Neither the name "Destroyer Studios" nor the names of its contributors
      may be used to endorse or promote products derived from this software
      without specific prior written permission.

    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
    AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
    ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
    LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
    CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
    SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
    INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
    CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
    ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
    POSSIBILITY OF SUCH DAMAGE.

    SUBSYSTEM:    nGenEx.lib
    FILE:         MachineInfo.cpp
    AUTHOR:       John DiCamillo


    OVERVIEW
    ========
    Collect and Display Machine, OS, and Driver Information
*/

#include "MemDebug.h"
#include "MachineInfo.h"
#include "Timesnap.h"

#define DIRECTINPUT_VERSION 0x0700

#include <stdio.h>
#include <ddraw.h>
#include <d3d9.h>
#include <dinput.h>
#include <winver.h>

// +--------------------------------------------------------------------+

void Print(const char* fmt, ...);

static int cpu_class=-1;
static int cpu_speed=-1;
static int platform=-1;
static int dx_version=-1;
static int total_ram=-1;

static OSVERSIONINFO os_ver      = { sizeof(OSVERSIONINFO) };
static SYSTEM_INFO   cpu_info;
static MEMORYSTATUS  mem_info    = { sizeof(MEMORYSTATUS) };

// +--------------------------------------------------------------------+

const char*
MachineInfo::GetShortDescription()
{
    static char desc[256];

    static const char* cpu_names[] = {
        "8088", 
        "8086", 
        "80286",
        "80386",
        "80486",
        "Pentium",
        "Pentium II",
        "Pentium 3",
        "Pentium 4"
    };

    static const char* os_names[] = {
        "DOS",
        "Windows 95",
        "Windows 98",
        "Windows NT",
        "Windows 2000",
        "Windows XP",
        "Windows XP x64",
        "Windows Vista",
        "Windows Seven",
        "Future Windows"
    };

    int cpu_index = GetCpuClass();
    if (cpu_index < 0)      cpu_index = 0;
    else if (cpu_index > 8) cpu_index = 8;

    int os_index = GetPlatform();
    if (os_index < 0)      os_index = 0;

    sprintf_s(desc, "%s %d MHz %d MB RAM %s",
    cpu_names[cpu_index],
    GetCpuSpeed(),
    GetTotalRam(),
    os_names[os_index]);

    return desc;
}

// +--------------------------------------------------------------------+

static void DescribeCpuMake();
static void DescribeOwner95();
static void DescribeOwnerNT();
static void DescribeDrivers95(const char* sType);
static void DescribeDriversNT(const char* sType);
static void DescribeDriverVersion(const char* file);
static void DescribeDXVersion(const char* component);

// wait for at least target_time,
// return the exact amount of time actually waited:

static double SpinWait(double target_time)
{
    double actual_time = 0;

    LARGE_INTEGER  ifreq;
    LARGE_INTEGER  cnt1;
    LARGE_INTEGER  cnt2;

    QueryPerformanceFrequency(&ifreq);
    double freq = (double) ifreq.QuadPart;

    QueryPerformanceCounter(&cnt1);

    do {
        QueryPerformanceCounter(&cnt2);

        double delta = (double) (cnt2.QuadPart - cnt1.QuadPart);
        actual_time  = delta / freq;
    }
    while (actual_time < target_time);

    return actual_time;
}

static double CalcCpuSpeed()
{
    DWORD clock1 = 0;
    DWORD clock2 = 0;

    TIMESNAP(clock1);

    double seconds = SpinWait(0.1);

    TIMESNAP(clock2);

    double clocks = clock2 - clock1;

    return (clocks/seconds);
}

/****************************************************************************
*
*      GetDXVersion
*
*  This function returns
*              0       Insufficient DirectX installed
*              9       At least DirectX 9 installed.
*
****************************************************************************/

DWORD GetDXVersion()
{
    HRESULT       hr      = 0;
    HINSTANCE     DDHinst = 0;
    LPDIRECT3D9   d3d9    = 0;
    OSVERSIONINFO osVer   = { sizeof(OSVERSIONINFO) };

    // First get the windows platform

    if (!GetVersionEx(&osVer))
    return 0;

    // NT versions do not support DirectX 9
    if (osVer.dwPlatformId == VER_PLATFORM_WIN32_NT) {
        if (osVer.dwMajorVersion <= 4)
        return 0;
    }

    DDHinst = LoadLibrary("D3D9.DLL");
    if (DDHinst == 0) {
        return 0;
    }

    FreeLibrary(DDHinst);
    return 9;
}


// +--------------------------------------------------------------------+

int
MachineInfo::GetCpuClass()
{
    if (cpu_class < 0) {
        GetSystemInfo(&cpu_info);

        if (cpu_info.wProcessorArchitecture != PROCESSOR_ARCHITECTURE_INTEL ||
                cpu_info.dwProcessorType        <  PROCESSOR_INTEL_PENTIUM)
        Print("INCOMPATIBLE CPU TYPE!\n");

        if (GetPlatform() < OS_WINNT)
        cpu_class = CPU_P5;

        else
        cpu_class = cpu_info.wProcessorLevel;
    }

    return cpu_class;
}

// +--------------------------------------------------------------------+

int
MachineInfo::GetCpuSpeed()
{
    if (cpu_speed < 0) {
        cpu_speed = (int) (CalcCpuSpeed() / 1e6);
    }

    return cpu_speed;
}

// +--------------------------------------------------------------------+

int
MachineInfo::GetTotalRam()
{
    if (total_ram < 0) {
        GlobalMemoryStatus(&mem_info);
        total_ram = (int) (mem_info.dwTotalPhys/(1024*1024)) + 1;
    }

    return total_ram;
}

// +--------------------------------------------------------------------+

int
MachineInfo::GetPlatform()
{
    if (platform < 0) {
        GetVersionEx(&os_ver);

        switch (os_ver.dwPlatformId) {
        default:
        case VER_PLATFORM_WIN32s:
        case VER_PLATFORM_WIN32_WINDOWS: {
                char msg[256];
                sprintf_s(msg, "Invalid Operating System Platform: %d\n", os_ver.dwPlatformId); //-V576
                Print(msg);
            }
            break;

        case VER_PLATFORM_WIN32_NT:
            if (os_ver.dwMajorVersion == 4)
            platform = OS_WINNT;
            else if (os_ver.dwMajorVersion == 5 && os_ver.dwMinorVersion == 0)
            platform = OS_WIN2K;
            else if (os_ver.dwMajorVersion == 5 && os_ver.dwMinorVersion == 1)
            platform = OS_WINXP;
            else if (os_ver.dwMajorVersion == 5 && os_ver.dwMinorVersion == 2)
            platform = OS_WINXP64;
            else if (os_ver.dwMajorVersion == 6 && os_ver.dwMinorVersion == 0)
            platform = OS_WINVISTA;
            else if (os_ver.dwMajorVersion == 6 && os_ver.dwMinorVersion == 1)
            platform = OS_WINSEVEN;
            else if (os_ver.dwMajorVersion >= 6)
            platform = OS_WINFUTURE;

            else {
                platform = OS_INVALID;

                Print("Invalid Operating System Platform (NT-series): %d.%d\n", os_ver.dwMajorVersion, os_ver.dwMinorVersion);
            }

            break;
        }
    }

    return platform;
}

// +--------------------------------------------------------------------+

int
MachineInfo::GetDirectXVersion()
{
    if (dx_version < 0) {
        dx_version = GetDXVersion();
    }

    return dx_version;
}

// +--------------------------------------------------------------------+

void
MachineInfo::DescribeMachine()
{
    GetPlatform();
    GetCpuClass();

    Print("+====================================================================+\n");
    Print("|                                                                    |\n");

    char txt[256];

    switch (platform) {
    case OS_WIN95:
        sprintf_s(txt, "Windows 95  version %d.%d.%d %s", //-V576
        os_ver.dwMajorVersion,
        os_ver.dwMinorVersion,
        LOWORD(os_ver.dwBuildNumber),
        os_ver.szCSDVersion);
        break;

    case OS_WIN98:
        sprintf_s(txt, "Windows 98  version %d.%d.%d %s", //-V576
        os_ver.dwMajorVersion,
        os_ver.dwMinorVersion,
        LOWORD(os_ver.dwBuildNumber),
        os_ver.szCSDVersion);
        break;

    case OS_WINNT:
        sprintf_s(txt, "Windows NT %d.%d (Build %d) %s", //-V576
        os_ver.dwMajorVersion,
        os_ver.dwMinorVersion,
        os_ver.dwBuildNumber,
        os_ver.szCSDVersion);
        break;

    case OS_WIN2K:
        sprintf_s(txt, "Windows 2000 %d.%d (Build %d) %s", //-V576
        os_ver.dwMajorVersion,
        os_ver.dwMinorVersion,
        os_ver.dwBuildNumber,
        os_ver.szCSDVersion);

    case OS_WINXP:
        sprintf_s(txt, "Windows XP %d.%d (Build %d) %s", //-V576
        os_ver.dwMajorVersion,
        os_ver.dwMinorVersion,
        os_ver.dwBuildNumber,
        os_ver.szCSDVersion);
        break;
    case OS_WINXP64:
        sprintf_s(txt, "Windows XP x64 %d.%d (Build %d) %s", //-V576
        os_ver.dwMajorVersion,
        os_ver.dwMinorVersion,
        os_ver.dwBuildNumber,
        os_ver.szCSDVersion);
        break;
    case OS_WINVISTA:
        sprintf_s(txt, "Windows Vista %d.%d (Build %d) %s", //-V576
        os_ver.dwMajorVersion,
        os_ver.dwMinorVersion,
        os_ver.dwBuildNumber,
        os_ver.szCSDVersion);
        break;
    case OS_WINSEVEN:
        sprintf_s(txt, "Windows 7 %d.%d (Build %d) %s", //-V576
        os_ver.dwMajorVersion,
        os_ver.dwMinorVersion,
        os_ver.dwBuildNumber,
        os_ver.szCSDVersion);
        break;
    case OS_WINFUTURE:
        sprintf_s(txt, "Windows from the future %d.%d (Build %d) %s", //-V576
        os_ver.dwMajorVersion,
        os_ver.dwMinorVersion,
        os_ver.dwBuildNumber,
        os_ver.szCSDVersion);
        break;

    default:
        sprintf_s(txt, "Unknown Operating System Platform");
        break;
    }

    Print("| %-66s |\n", txt);
    Print("|                                                                    |\n");

    if (platform == OS_WIN95 || platform == OS_WIN98)
    DescribeOwner95();
    else
    DescribeOwnerNT();

    sprintf_s(txt, "CPUs Detected: %d    CPU Level: %d.%d.%d    CPU Speed: %d", //-V576
    cpu_info.dwNumberOfProcessors,
    cpu_info.wProcessorLevel,
    cpu_info.wProcessorRevision >> 8,
    cpu_info.wProcessorRevision & 0xff,
    GetCpuSpeed() + 1);

    Print("| %-66s |\n", txt);
    DescribeCpuMake();

    GlobalMemoryStatus(&mem_info);
    total_ram      = (int) (mem_info.dwTotalPhys/(1024*1024)) + 1;
    int swap_max   = (int) (mem_info.dwTotalPageFile/(1024*1024));
    int swap_avail = (int) (mem_info.dwAvailPageFile/(1024*1024));

    sprintf_s(txt, "%d MB RAM    %d MB Max Swap    %d MB Avail Swap", 
    total_ram, swap_max, swap_avail);


    Print("| %-66s |\n", txt);

    Print("|                                                                    |\n");
    Print("| DirectX %d installed.                                               |\n",
    GetDirectXVersion());
    DescribeDXVersion("DDRAW");
    DescribeDXVersion("D3DIM");
    DescribeDXVersion("DINPUT");
    DescribeDXVersion("DPLAY");
    DescribeDXVersion("DSOUND");
    DescribeDXVersion("DMUSIC");
    DescribeDXVersion("DSHOW");
    Print("|                                                                    |\n");


    if (platform == OS_WIN95 || platform == OS_WIN98) {
        DescribeDrivers95("Display");
        DescribeDrivers95("Media");
        DescribeDrivers95("Monitor");
        DescribeDrivers95("Multimedia");
    }
    else {
        DescribeDriversNT("");
    }

    Print("+====================================================================+\n");
    Print("\n");
}

// +--------------------------------------------------------------------+

static void DescribeCpuMake()
{
    HKEY  hkWin;
    char  sProcessor[256] = "";
    char  sMMXInfo[256]   = "";
    char  sVendor[256]    = "";
    DWORD dwSize;

    if (RegOpenKeyEx(HKEY_LOCAL_MACHINE,
                "Hardware\\Description\\System\\CentralProcessor\\0",
                0,
                KEY_READ,
                &hkWin) == ERROR_SUCCESS) {

        dwSize = 256;
        RegQueryValueEx(hkWin,
        "Identifier",
        NULL,
        NULL,
        (LPBYTE) sProcessor,
        &dwSize);

        dwSize = 256;
        RegQueryValueEx(hkWin,
        "MMXIdentifier",
        NULL,
        NULL,
        (LPBYTE) sMMXInfo,
        &dwSize);

        dwSize = 256;
        RegQueryValueEx(hkWin,
        "VendorIdentifier",
        NULL,
        NULL,
        (LPBYTE) sVendor,
        &dwSize);

        RegCloseKey(hkWin);
    }

    if (sProcessor[0])   Print("| %-66s |\n", sProcessor);
    if (sMMXInfo[0])     Print("| %-66s |\n", sMMXInfo);
    if (sVendor[0])      Print("| %-66s |\n", sVendor);

    if (sProcessor[0] || sMMXInfo[0] || sVendor[0])
    Print("|                                                                    |\n");
}

// +--------------------------------------------------------------------+

static void DescribeOwner95()
{
    HKEY  hkWin;
    char  sRegisteredOwner[256] = "";
    char  sRegisteredOrganization[256] = "";
    DWORD dwSize;

    if (RegOpenKeyEx(HKEY_LOCAL_MACHINE,
                "SOFTWARE\\Microsoft\\Windows\\CurrentVersion",
                0,
                KEY_READ,
                &hkWin) == ERROR_SUCCESS) {

        dwSize = 256;
        RegQueryValueEx(hkWin,
        "RegisteredOwner",
        NULL,
        NULL,
        (LPBYTE) sRegisteredOwner,
        &dwSize);

        dwSize = 256;
        RegQueryValueEx(hkWin,
        "RegisteredOrganization",
        NULL,
        NULL,
        (LPBYTE) sRegisteredOrganization,
        &dwSize);

        RegCloseKey(hkWin);
    }
    else {
        Print("Could not access registered owner\n");
    }

    if (sRegisteredOwner[0]) {
        char  txt[256];
        sprintf_s(txt, "Registered Owner: %s, %s", sRegisteredOwner, sRegisteredOrganization);
        Print("| %-66s |\n", txt);
        Print("|                                                                    |\n");
    }
}

static void DescribeOwnerNT()
{
    HKEY  hkWin;
    char  sRegisteredOwner[256] = "";
    char  sRegisteredOrganization[256] = "";
    DWORD dwSize;

    if (RegOpenKeyEx(HKEY_LOCAL_MACHINE,
                "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion",
                0,
                KEY_READ,
                &hkWin) == ERROR_SUCCESS) {

        dwSize = 256;
        RegQueryValueEx(hkWin,
        "RegisteredOwner",
        NULL,
        NULL,
        (LPBYTE) sRegisteredOwner,
        &dwSize);

        dwSize = 256;
        RegQueryValueEx(hkWin,
        "RegisteredOrganization",
        NULL,
        NULL,
        (LPBYTE) sRegisteredOrganization,
        &dwSize);

        RegCloseKey(hkWin);
    }
    else {
        Print("Could not access registered owner\n");
    }

    if (sRegisteredOwner[0]) {
        char  txt[256];
        sprintf_s(txt, "Registered Owner: %s, %s", sRegisteredOwner, sRegisteredOrganization);
        Print("| %-66s |\n", txt);
        Print("|                                                                    |\n");
    }
}

// +--------------------------------------------------------------------+

static void DescribeDrivers95(const char* sType)
{
    HKEY  hkWin, hkSub;
    int   nKey = 0;
    char  sKey[256];
    char  sSub[256];
    char  sDriver[256];
    char  txt[256];
    DWORD dwSize;
    int   worked;

    // describe the video driver(s):
    do {
        worked = 0;

        sprintf_s(sKey, "System\\CurrentControlSet\\Services\\Class\\%s\\%04X", sType, nKey);
        sprintf_s(sSub, "System\\CurrentControlSet\\Services\\Class\\%s\\%04X\\DEFAULT", sType, nKey);

        if (RegOpenKeyEx(HKEY_LOCAL_MACHINE,
                    sKey,
                    0,
                    KEY_READ,
                    &hkWin) == ERROR_SUCCESS) {

            dwSize = 256;
            RegQueryValueEx(hkWin,
            "DriverDesc",
            NULL,
            NULL,
            (LPBYTE) sDriver,
            &dwSize);

            if (sDriver[0]) {
                sprintf_s(txt, "*  %s", sDriver);
                Print("| %-66s |\n", txt);
                worked = 1;
            }

            // try to find the driver file name:
            if (worked) {
                ZeroMemory(sDriver, sizeof(sDriver));

                dwSize = 256;
                DWORD err = RegQueryValueEx(hkWin, "Driver", NULL, NULL, (LPBYTE) sDriver, &dwSize);

                if (err != ERROR_SUCCESS) {
                    dwSize = 256;
                    err = RegQueryValueEx(hkWin, "DeviceDriver", NULL, NULL, (LPBYTE) sDriver, &dwSize);
                }

                if (err != ERROR_SUCCESS) {
                    dwSize = 256;
                    err = RegQueryValueEx(hkWin, "drv", NULL, NULL, (LPBYTE) sDriver, &dwSize);
                }

                if (err != ERROR_SUCCESS) {
                    if (RegOpenKeyEx(HKEY_LOCAL_MACHINE,
                                sSub,
                                0,
                                KEY_READ,
                                &hkSub) == ERROR_SUCCESS) {

                        dwSize = 256;
                        err = RegQueryValueEx(hkSub, "drv", NULL, NULL, (LPBYTE) sDriver, &dwSize);

                        RegCloseKey(hkSub);
                    }
                }

                // if we found it, try to display version info:
                if (err == ERROR_SUCCESS) {
                    DescribeDriverVersion(sDriver);
                }

                Print("|                                                                    |\n");
            }

            RegCloseKey(hkWin);
        }

        nKey++;
    }
    while (worked);
}

static void DescribeDriversNT(const char* sType)
{
    Print("|                                                                    |\n");

    HKEY  hkWin;
    char  sVideo[256] = "";
    char  sDriver[256] = "";
    DWORD dwSize = NULL;

    // find the pointer to the video driver:
    if (RegOpenKeyEx(HKEY_LOCAL_MACHINE,
                "HARDWARE\\DEVICEMAP\\VIDEO",
                0,
                KEY_READ,
                &hkWin) == ERROR_SUCCESS) {

        dwSize = 256;
        RegQueryValueEx(hkWin,
        "\\Device\\Video0",
        NULL,
        NULL,
        (LPBYTE) sVideo,
        &dwSize);

        RegCloseKey(hkWin);
    }

    // follow the pointer and get the driver description:
    if (dwSize && sVideo[0]) {
        const char* sLeader = "\\REGISTRY\\Machine\\";
        int         nLeader = strlen(sLeader);

        if (_strnicmp(sVideo, sLeader, nLeader) == 0) {
            if (RegOpenKeyEx(HKEY_LOCAL_MACHINE,
                        sVideo + nLeader,
                        0,
                        KEY_READ,
                        &hkWin) == ERROR_SUCCESS) {

                dwSize = 256;
                RegQueryValueEx(hkWin,
                "Device Description",
                NULL,
                NULL,
                (LPBYTE) sDriver,
                &dwSize);

                RegCloseKey(hkWin);
            }
        }
    }

    if (sDriver[0]) {
        Print("| %-66s |\n", sDriver);
        Print("|                                                                    |\n");
    }
}

// +--------------------------------------------------------------------+

static char sTranslation[16];

static void GetTranslation(const LPBYTE pBlock)
{
    LPBYTE  sData = NULL;
    UINT    lenData = 0;

    if (VerQueryValue(pBlock, "\\VarFileInfo\\Translation",
                (LPVOID*) &sData, &lenData)) {

        if (lenData && sData) {
            sprintf_s(sTranslation, "%02X%02X%02X%02X", sData[1], sData[0], sData[3], sData[2]);
        }
    }
}

void DisplayVersionString(const LPBYTE pBlock, LPTSTR sSection)
{
    char     txt[256];
    char     sFullSection[256];

    sprintf_s(sFullSection, "\\StringFileInfo\\%s\\%s", sTranslation, sSection);

    LPBYTE  sData = NULL;
    UINT    lenData = 0;
    DWORD   dwErr = 0;

    if (VerQueryValue(pBlock, sFullSection, (LPVOID*) &sData, &lenData)) {
        if (lenData && sData) {
            sprintf_s(txt, "%-16s %s", sSection, sData);
            Print("|       %-60s |\n", txt);
        }
    }
}

static void DescribeDriverVersion(const char* file)
{
    DWORD dwHandle = 0;
    TCHAR szFile[512];

    strcpy_s(szFile, file);

    int nBytes = GetFileVersionInfoSize(szFile, &dwHandle);

    if (nBytes <= 0) {
        char  szWinDir[256];
        GetSystemDirectory(szWinDir, 256);
        sprintf_s(szFile, "%s\\%s", szWinDir, file);

        nBytes = GetFileVersionInfoSize(szFile, &dwHandle);

        if (nBytes <= 0)
        return;
    }

    LPBYTE pBlock = new(__FILE__,__LINE__) BYTE[nBytes];

    if (pBlock && GetFileVersionInfo(szFile, dwHandle, nBytes, (LPVOID) pBlock)) {
        GetTranslation(pBlock);
        DisplayVersionString(pBlock, "CompanyName");
        //      DisplayVersionString(pBlock, "FileDescription");
        DisplayVersionString(pBlock, "FileVersion");
        //      DisplayVersionString(pBlock, "InternalName");
        //      DisplayVersionString(pBlock, "LegalCopyright");
        //      DisplayVersionString(pBlock, "OriginalFilename");
        //      DisplayVersionString(pBlock, "ProductName");
        //      DisplayVersionString(pBlock, "ProductVersion");
        //      DisplayVersionString(pBlock, "Comments");
        //      DisplayVersionString(pBlock, "LegalTrademarks");
        //      DisplayVersionString(pBlock, "PrivateBuild");
        //      DisplayVersionString(pBlock, "SpecialBuild");
    }

    delete [] pBlock;
}

static void DescribeDXVersion(const char* component)
{
    DWORD dwHandle = 0;
    char  szFile[512];
    char  szWinDir[512];

    GetSystemDirectory(szWinDir, 512);

    sprintf_s(szFile, "%s\\%s.dll", szWinDir, component);

    int nBytes = GetFileVersionInfoSize(szFile, &dwHandle);

    if (nBytes <= 0) {
        return;
    }

    LPBYTE pBlock = new(__FILE__,__LINE__) BYTE[nBytes];

    if (pBlock && GetFileVersionInfo(szFile, dwHandle, nBytes, (LPVOID) pBlock)) {
        GetTranslation(pBlock);

        char     txt[256];
        char     sFullSection[256];
        LPBYTE   sData = NULL;
        UINT     lenData = 0;
        DWORD    dwErr = 0;

        sprintf_s(sFullSection, "\\StringFileInfo\\%s\\FileVersion", sTranslation);

        if (VerQueryValue(pBlock, sFullSection, (LPVOID*) &sData, &lenData)) {
            if (lenData && sData) {
                sprintf_s(txt, "%-8s%s", component, sData);
                Print("|   %-64s |\n", txt);
            }
        }
    }

    delete [] pBlock;
}